﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Globalization;

namespace ftpd_console
{
    class FTP_Listener
    {
        //Список всех слушателей
        private static LinkedList<FTP_Listener> listeners = new LinkedList<FTP_Listener>();

        private static string folder_working_directory = @"c:\ftp";
        private static string bind_ipadress;

        //=== Локальные
        private Thread thrd;
        private Socket socket;
        private Socket dataSocket;
        //Поток команд
        private NetworkStream stream;
        //private System.IO.StreamReader reader;
        //private System.IO.StreamWriter writer;
        //Поток данных
        private NetworkStream dataStream;
        private TcpListener dataListener;
        private int dataPort;
        //Остальное
        private string word = null;
        private string folder_current = @"\";
        private int connectionType = 0;
        private int connectionMode = 0;

        //Информация о клиенте
        private string user = null;
        private string pass = null;
        private bool bLogged = false;
        private bool bQuit = false;

        //Конструктор
        public FTP_Listener(Socket s)
        {
            socket = s;

            //Создаем поток
            thrd = new Thread(this.run);
            thrd.Start();
            Console.WriteLine("Listener: Start!");

            //Добавляем в список
            listeners.AddLast(this);
        }

        //Главный вход ===
        void run()
        {
            //Инициализируем соединения
            try
            {
                stream = new NetworkStream(socket);

                //Посылаем приветствие
                FTP_Messages.Send(stream, 220);

                //Пока есть соединение
                do
                {
                    //Если есть данные для чтения, то читаем из потока
                    if (stream.DataAvailable)
                    {
                        word = FTP_Messages.ReadLine(stream).Trim();
                    }
                    //Иначе ждем
                    else
                    {
                        try
                        {
                            Thread.Sleep(10);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("Listener exception!");
                        }

                        continue;
                    }


                    Console.WriteLine("Client: " + word);

                    //Авторизация
                    if (!bLogged)
                    {
                        //Берем первое слово до пробела
                        switch (word.Split(' ')[0])
                        {
                            //Ввод логина
                            case "USER":
                                ftp_USER(Helpers.GetParameter(word));
                                break;

                            //Ввод пароля и проверка связки логин-пароль
                            case "PASS":
                                ftp_PASS(Helpers.GetParameter(word));
                                break;

                            //Закрыть соединение
                            case "QUIT":
                                ftp_QUIT();
                                break;

                            //Команды без авторизации
                            default:
                                FTP_Messages.Send(stream, 530);
                                break;
                        }
                    }
                    //Взаимодействие
                    else
                    {
                        //Берем первое слово до пробела
                        switch (word.Split(' ')[0])
                        {
                            //Ввод логина
                            case "USER":
                                ftp_USER(Helpers.GetParameter(word));
                                break;

                            //Ввод пароля
                            case "PASS":
                                ftp_PASS(Helpers.GetParameter(word));
                                break;

                            //Информация о сервере
                            case "SYST":
                                ftp_SYST();
                                break;

                            //Доступные команды
                            case "HELP":
                                ftp_HELP();
                                break;

                            //Послать инфу о дополнениях
                            case "FEAT":
                                ftp_FEAT();
                                break;

                            //Задать тип передачи данных
                            case "TYPE":
                                ftp_TYPE(Helpers.GetParameter(word));
                                break;

                            //Имя текущего каталога
                            case "PWD":
                                ftp_PWD();
                                break;

                            //Показать листинг текущего каталога
                            case "LIST":
                                ftp_LIST();
                                break;

                            //Выбор пассивного подключения
                            case "PASV":
                                ftp_PASV();
                                break;

                            //Смена рабочего каталога
                            case "CWD":
                                ftp_CWD(Helpers.GetParameter(word));
                                break;

                            //Перейти на каталог выше
                            case "CDUP":
                                ftp_CDUP();
                                break;

                            //Передача файла клиенту
                            case "RETR":
                                ftp_RETR(Helpers.GetParameter(word));
                                break;

                            //Получение файла от клиента
                            case "STOR":
                                ftp_STOR(Helpers.GetParameter(word));
                                break;

                            //Удаление файла на сервере
                            case "DELE":
                                ftp_DELE(Helpers.GetParameter(word));
                                break;

                            //Создание нового каталога
                            case "MKD":
                                ftp_MKD(Helpers.GetParameter(word));
                                break;

                            //Удаление каталога
                            case "RMD":
                                ftp_RMD(Helpers.GetParameter(word));
                                break;

                            //Закрыть соединение
                            case "QUIT":
                                ftp_QUIT();
                                break;

                            //Закрыть соединение
                            case "BYE":
                                ftp_QUIT();
                                break;

                            case "REST":
                                //???
                                FTP_Messages.Send(stream, 200);
                                break;

                            //Неизвестная команда
                            default:
                                FTP_Messages.Send(stream, 503);
                                Console.WriteLine("Listener: comand not implemented!");
                                break;
                        }
                    }

                    //Сбрасываем строку
                    word = null;

                } while (!bQuit);
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception in Run() method");
                //thrd.Abort();
                //Console.WriteLine(e.StackTrace);
            }
            finally
            {
                //Закрываем все соединения
                if(stream != null) stream.Close();
                if(socket != null) socket.Close();
            }
        }



        //Пройти авторизацию
        bool Authorize()
        {
            if (user == null || pass == null) return false;
            FileStream fs = new FileStream(@"c:\users.txt", FileMode.Open);
            StreamReader reader = new StreamReader(fs);
            string line;
            string[] words;
            do{
                line = reader.ReadLine();
                if (line != null)
                {
                    words = line.Split(' ');
                    if (user.Equals(words[0]) && pass.Equals(words[1]))
                    {
                        fs.Close();
                        return true;
                    }
                }
            } while(!reader.EndOfStream);
            fs.Close();
            return false;
            //Old
            //if (user.Equals("jeronima") && pass.Equals("123"))
            //{
            //    return true;
            //}
            //return false;
        }

        //Задать тип соединения
        bool SetConnectionType(string type)
        {
            switch (type)
            {
                case "A":
                    // ASCII text
                    connectionType = 0;
                    break;
                case "I":
                    // image (binary data)
                    connectionType = 1;
                    break;
                default:
                    return false;
            }
            return true;
        }

        string GetConnectionType()
        {
            switch (connectionType)
            {
                case 0:
                    return "A";
                case 1:
                    return "I";
                default:
                    return "";
            }
        }

        //=== Команды FTP сервера ===

        //Команда USER - задает имя пользователя
        void ftp_USER(string param)
        {
            //Если еще не авторизированы
            if (!bLogged)
            {
                user = param;
                FTP_Messages.Send(stream, 331, param);
            }
            //Уже авторизированы
            else
            {
                FTP_Messages.Send(stream, 230);
            }
        }

        //Команда PASS - задает пароль и запускает авторизацию
        void ftp_PASS(string param)
        {
            //Еще не авторизированы
            if (!bLogged)
            {
                pass = param;
                //Авторизируем
                if (Authorize())
                {
                    FTP_Messages.Send(stream, 230, user);
                    bLogged = true;
                }
                else
                {
                    FTP_Messages.Send(stream, 530);
                }
            }
            //Уже авторизированны
            else
            {
                FTP_Messages.Send(stream, 230, user);
            }
        }

        //Команда QUIT - закрывает соединение с клиентом
        void ftp_QUIT()
        {
            FTP_Messages.Send(stream, 221);
            bQuit = true;
        }

        //Команда SYST - возвращает тип системы сервера
        void ftp_SYST()
        {
            FTP_Messages.Send(stream, 215);
        }

        //Команда STAT - возвращает понятную для пользователя информацию, игнорируется клиентом
        void ftp_STAT()
        {

        }

        //Команда MLSD - возвращает листинг директории
        void ftp_MLSD()
        {

        }

        //Команда LIST - возвращает листинг директории
        void ftp_LIST()
        {
            //Берем информацию о каталоге
            DirectoryInfo directory = new DirectoryInfo(folder_working_directory + folder_current);

            
            //Читаем все директории
            DirectoryInfo[] dirs = directory.GetDirectories();
            //Читаем все файлы
            FileInfo[] files = directory.GetFiles();

            //Сообщаем, что начинаем передавать список файлов
            FTP_Messages.Send(stream, 150);
            //NetworkStream dataStream = CreateDataStream();
            StreamWriter dataWriter = new StreamWriter(dataStream);//, Encoding.UTF8);

            //Если есть хотя бы одна директория
            if (dirs != null)
            {
                string line;
                DateTime date;

                foreach (DirectoryInfo dir in dirs)
                {
                    line = "drw-r--r-- 1 owner group";              //Это директория
                    date = dir.LastWriteTime;                      //Дата изменения директории
                    line += "4096".PadLeft(13);     //Длинна директории в байтах на 13 символов
                    line += date.ToString(" MMM dd HH:mm ", new CultureInfo("en-US"));
                    line += dir.Name;                              //Имя файла
                    line += "\r\n";
                    byte[] bytes = Encoding.Default.GetBytes(line.ToCharArray());
                    dataStream.Write(bytes, 0, bytes.Length);
                    Console.Write(Encoding.Default.GetString(bytes, 0, bytes.Length));
                }
            }

            //Если есть хотя бы один файл
            if (files != null)
            {
                string line;
                DateTime date;

                foreach(FileInfo file in files)
                {
                    line = "-rw-r--r-- 1 owner group";              //Это файл
                    date = file.LastWriteTime;                      //Дата изменения файла
                    line += file.Length.ToString().PadLeft(13);     //Длинна файла в байтах на 13 символов
                    line += date.ToString(" MMM dd HH:mm ", new CultureInfo("en-US"));
                    line += file.Name;                              //Имя файла
                    line += "\r\n";
                    byte[] bytes = Encoding.Default.GetBytes(line.ToCharArray());
                    dataStream.Write(bytes, 0, bytes.Length);
                    Console.Write(Encoding.ASCII.GetString(bytes,0,bytes.Length));
                }
            }

            //Сообщаем, что закончили передачу
            FTP_Messages.Send(stream, 226);
            //Закрываем соединение
            CloseDataStream();
        }

        //Команда PWD - возвращает имя текущего каталога
        void ftp_PWD()
        {
            FTP_Messages.Send(stream, 257, folder_current);
        }

        //Команда TYPE - установка способа передачи информации
        void ftp_TYPE(string param)
        {
            if (SetConnectionType(param))
            {
                FTP_Messages.Send(stream, 200);
            }
            else
            {
                FTP_Messages.Send(stream, 500);
            }
        }

        //Команда FEAT - возвращает возможности сервера
        void ftp_FEAT()
        {
            FTP_Messages.Send(stream, 211);
        }

        //Команда PASV - выбор пассивного режима работы сервера
        void ftp_PASV()
        {
            //Резервирование нового порта для данных
            dataPort = Helpers.GetDataPort();
            FTP_Messages.Send(stream, 227, bind_ipadress, dataPort.ToString());
            CreateDataStream();
            connectionMode = 0; //Passive mode
        }

        //Команда MKD - создание директории
        void ftp_MKD(string param)
        {
            //Если не хватает аргументов
            if (param.Length < 1)
            {
                FTP_Messages.Send(stream, 501);
                Console.WriteLine("MKD - no parameters!");
                return;
            }

            DirectoryInfo dir = Directory.CreateDirectory(folder_working_directory + folder_current + param);
            if (dir.Exists)
            {
                FTP_Messages.Send(stream, 200);
                return;
            }
        }

        //Команда RMD - удаление каталога
        void ftp_RMD(string param)
        {
            //Если не хватает аргументов
            if (param.Length < 1)
            {
                FTP_Messages.Send(stream, 501);
                Console.WriteLine("RMD - no parameters!");
                return;
            }

            try
            {
                Directory.Delete(folder_working_directory + folder_current + param);
            }
            catch (DirectoryNotFoundException de)
            {
                FTP_Messages.Send(stream, 553);
                Console.WriteLine(de.Message);
                return;
            }
            catch (Exception e)
            {
                FTP_Messages.Send(stream, 553);
                Console.WriteLine("Folder can't be removed! " + e.Message);
                return;
            }
            FTP_Messages.Send(stream, 200);
        }

        //Команда DELE - удаление файла на сервере
        void ftp_DELE(string param)
        {
            //Если не хватает аргументов
            if (param.Length < 1)
            {
                FTP_Messages.Send(stream, 501);
                Console.WriteLine("DELE - no parameters!");
                return;
            }
            //Если файл существует
            FileInfo file = new FileInfo(folder_working_directory + folder_current + param);
            if (file.Exists)
            {
                //Удаляем его
                file.Delete();
                FTP_Messages.Send(stream, 200);
                return;
            }
            else
            {
                FTP_Messages.Send(stream, 550, param);
                return;
            }
        }

        //Команда CDUP - перейти на каталог выше
        void ftp_CDUP()
        {
            Console.WriteLine(folder_current);
            folder_current = Helpers.GetPreviousDir(folder_current);
            FTP_Messages.Send(stream, 200);
            Console.WriteLine(folder_current);
        }

        //Команда CWD - смена рабочего каталога
        void ftp_CWD(string param)
        {
            //Если не хватает аргументов
            if (param.Length < 1)
            {
                folder_current = @"\";
                FTP_Messages.Send(stream, 257, folder_current);
                return;
            }

            if (param.Trim().Equals(@"\"))
            {
                folder_current = @"\";
                FTP_Messages.Send(stream, 200);
                return;
            }
            //Если хотят подняться наверх
            else if (param.Trim().Equals(".."))
            {
                folder_current = Helpers.GetPreviousDir(folder_current);
                FTP_Messages.Send(stream, 200);
                return;
            }
            //Если хотят на самый верх
            else if (param.Trim().Equals("."))
            {
                folder_current = @"\";
                FTP_Messages.Send(stream, 200);
                return;
            }
            //Если директория существует, то поменять
            else if (Directory.Exists(folder_working_directory + folder_current + param))
            {
                folder_current += param + @"\";
                FTP_Messages.Send(stream, 200);
                return;
            }
            //Иначе выслать ошибку
            else
            {
                FTP_Messages.Send(stream, 550, folder_current + param);
            }
        }

        //Команда STOR - получает файл от клиента и сохраняет в текущий каталог
        void ftp_STOR(string param)
        {
            if (param.Length < 1)
            {
                Console.WriteLine("Listener: STOR no parameters!");
                FTP_Messages.Send(stream, 501);
            }
            else
            {
                //Сообщаем, что готовы получить файл и создаем подключение
                FTP_Messages.Send(stream, 150);
                try
                {
                    //Подключаемся к файлу
                    FileStream fstream = new FileStream(folder_working_directory + folder_current + param, FileMode.Create);
                    //Создаем буфер чтения из файла
                    byte[] bytes = new byte[Helpers.buffer_lenght];
                    int bytes_lenght;
                    do
                    {
                        //Читаем байты из потока данных
                        bytes_lenght = dataStream.Read(bytes, 0, Helpers.buffer_lenght);
                        if (bytes_lenght > 0)
                        {
                            //Записываем очередную порцию байт в файл
                            try
                            {
                                fstream.Write(bytes, 0, bytes_lenght);
                            }
                            catch (IOException e)
                            {
                                Console.WriteLine("IOException!");
                                Console.WriteLine(e.StackTrace);
                                fstream.Close();
                                CloseDataStream();
                                return;
                            }
                        }
                    } while (dataStream.DataAvailable);
                    fstream.Close();
                }
                catch (IOException e)
                {
                    Console.WriteLine("Can't create a file Exception");
                    Console.WriteLine(e.StackTrace);
                }
                FTP_Messages.Send(stream, 226);
                CloseDataStream();
            }
        }

        //Команда RETR - отправляет файл клиенту
        void ftp_RETR(string param)
        {
            if (param.Length < 1)
            {
                Console.WriteLine("Listener: RETR no parameters!");
                FTP_Messages.Send(stream, 501);
            }
            else
            {
                //Сообщаем, что готовы отдать файл и создаем подключение
                FTP_Messages.Send(stream, 150);
                try
                {
                    //Подключаемся к файлу
                    FileStream fstream = new FileStream(folder_working_directory + folder_current + param, FileMode.Open);
                    //Создаем буфер чтения из файла
                    byte[] bytes = new byte[Helpers.buffer_lenght];
                    //Сколько прочитали в буффер из файла
                    int bytes_lenght;
                    do
                    {
                        bytes_lenght = fstream.Read(bytes, 0, Helpers.buffer_lenght);
                        if (bytes_lenght > 0)
                        {
                            //Посылаем очередную порцию
                            try
                            {
                                dataStream.Write(bytes, 0, bytes_lenght);
                            }
                            catch (IOException e)
                            {
                                Console.WriteLine("IOException!");
                                fstream.Close();
                                CloseDataStream();
                                return;
                            }
                            
                        }
                    } while (bytes_lenght > 0);
                    
                    //Закрываем файловый поток
                    fstream.Close();
                }
                catch (FileNotFoundException fe)
                {
                    FTP_Messages.Send(stream, 550, param);
                    Console.WriteLine(fe.FileName + " not found!");
                }
                FTP_Messages.Send(stream, 226);
                CloseDataStream();
            }


        }


        //Команда HELP - возвращает список доступных команд сервера
        void ftp_HELP()
        {
            FTP_Messages.Send(stream, 214);
        }

        //Инициализируем поток данных
        NetworkStream CreateDataStream()
        {
            //Создаем слушатель для подключения потока данных
            Console.WriteLine("Listener: " + bind_ipadress + " : " + dataPort);
            try
            {
                dataListener = new TcpListener(IPAddress.Parse(bind_ipadress), dataPort);
                dataListener.Start();
                //Ждем соединения
                dataSocket = dataListener.AcceptSocket();
                dataListener.Stop();
                if (dataSocket.Connected)
                {
                    //Возвращаем поток
                    Console.WriteLine("Listener: New datastream created!");
                    dataStream = new NetworkStream(dataSocket);
                    return dataStream;
                }
                else
                {
                    //Не удалось подключить
                    Console.WriteLine("Listener: Data stream connection error!");
                    return null;
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("Listener: SocketException!");
            }
            return null;
        }

        //Закрывает поток данных
        void CloseDataStream()
        {
            if (dataSocket.Connected)
            {
                dataSocket.Close();
            }
            Console.WriteLine("Listener: Data connection closed!");
        }

        //Экстренное завершение потока
        void Close()
        {
            if(stream != null) stream.Close();
            if(socket != null) socket.Close();
            if (dataListener != null) dataListener.Stop();
            if(dataStream != null) dataStream.Close();
            if(dataSocket != null) dataSocket.Close();
            
            Console.WriteLine("Connection closed!");
            thrd.Abort();
            Console.WriteLine("Listener Abort!");
        }

        //Экстренное завершение всех потоков
        public static void CloseAll()
        {
            foreach (FTP_Listener listener in listeners)
            {
                listener.Close();
            }
        }

        public static void SetBindIp(string ip)
        {
            bind_ipadress = ip;
        }

    }
}
